/*-------------------------------------------------------------------------
 *
 * paramassign.c
 *		在规划过程中分配 PARAM_EXEC 插槽的函数。
 *
 * 该模块负责管理三个规划器数据结构：
 *
 * root->glob->paramExecTypes: 记录PARAM_EXEC插槽的实际分配。
 * 第 i 个列表元素保存第 i 个参数插槽的数据类型 OID。
 * （元素可以是 InvalidOid，如果它们表示需要用于
 * chgParam 信号的插槽，但在运行时永远不会保存值。）这个列表
 * 是全局的，因为执行器只有一个 PARAM_EXEC 数组。
 * 分配对计划是永久的：我们从不删除已添加的条目。
 *
 * root->plan_params: 记录的 PlannerParamItem 节点列表，需要提供给
 * 下层子查询的 Vars 和 PlaceHolderVars，连同为每个这样的值
 * 使用的 PARAM_EXEC 编号。
 * 在规划子查询时将元素添加到此列表，并在每个子查询完成后
 * 将列表重置为空。
 *
 * root->curOuterParams: 记录 NestLoopParam 节点的列表，记录某些
 * 外层 NestLoop 需要传递给其右侧较低计划节点的 Vars 和
 * PlaceHolderVars。随着 createplan.c 创建需要这些参数的
 * 较低 Plan 节点，元素被添加到此列表，并在创建将提供这些
 * 值的 NestLoop Plan 节点时被移除。
 *
 * 后两个数据结构用于防止创建多个 PARAM_EXEC 插槽
 * （每个插槽都需要工作来填充），当同一个上层
 * SubPlan 或 NestLoop 提供一个在其子计划节点的多个地方
 * 被引用的值时。然而，当同一个 Var 必须由不同的 SubPlan
 * 或 NestLoop 父节点提供给不同的子计划树时，我们不会识别
 * 任何共性；将创建一个新的 plan_params 或 curOuterParams
 * 条目（因为旧的在我们完成处理早期的 SubPlan 或 NestLoop
 * 时已被移除），并将分配一个新的 PARAM_EXEC 编号。
 * 曾经我们试图在这种情况下避免分配重复的 PARAM_EXEC 编号，
 * 但避免由于重叠的参数生命周期导致的错误比看起来更困难，
 * 所以我们不再冒这个风险。无论如何，最小化 PARAM_EXEC
 * 插槽的数量并不会真的节省多少执行器工作。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/util/paramassign.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "nodes/nodeFuncs.h"
#include "nodes/plannodes.h"
#include "optimizer/paramassign.h"
#include "optimizer/placeholder.h"
#include "rewrite/rewriteManip.h"


/*
 * 选择一个 PARAM_EXEC 数字来标识当前子查询中的给定 Var 作为参数。
 * （它可能已经有一个。）
 * 在适当的上层 root->plan_params 中记录对该 Var 的需求。
 */
static int fc_assign_param_for_var(PlannerInfo *fc_root, Var *fc_var)
{
	ListCell   *fc_ppl;
	PlannerParamItem *fc_pitem;
	Index		fc_levelsup;

	/* 找到 Var 所属的查询级别 */
	for (fc_levelsup = fc_var->varlevelsup; fc_levelsup > 0; fc_levelsup--)
		fc_root = fc_root->parent_root;

	
/*如果那里已经有一个匹配的 PlannerParamItem，就直接使用它*/
	foreach(fc_ppl, fc_root->plan_params)
	{
		fc_pitem = (PlannerParamItem *) lfirst(fc_ppl);
		if (IsA(fc_pitem->item, Var))
		{
			Var		   *fc_pvar = (Var *) fc_pitem->item;

			/*
			 * 这个比较必须与 _equalVar() 匹配，除了忽略
			 * varlevelsup。注意 _equalVar() 忽略 varnosyn，
			 * varattnosyn 和 location，因此这个也是。
			 */
			if (fc_pvar->varno == fc_var->varno &&
				fc_pvar->varattno == fc_var->varattno &&
				fc_pvar->vartype == fc_var->vartype &&
				fc_pvar->vartypmod == fc_var->vartypmod &&
				fc_pvar->varcollid == fc_var->varcollid)
				return fc_pitem->paramId;
		}
	}

	/* 不，创建一个新的 */
	fc_var = copyObject(fc_var);
	fc_var->varlevelsup = 0;

	fc_pitem = makeNode(PlannerParamItem);
	fc_pitem->item = (Node *) fc_var;
	fc_pitem->paramId = list_length(fc_root->glob->paramExecTypes);
	fc_root->glob->paramExecTypes = lappend_oid(fc_root->glob->paramExecTypes,
											 fc_var->vartype);

	fc_root->plan_params = lappend(fc_root->plan_params, fc_pitem);

	return fc_pitem->paramId;
}

/*
 * 生成一个 Param 节点以替换给定的 Var，
 * 预计它的 varlevelsup > 0（即，它不是本地的）。
 * 在适当的上层 root->plan_params 中记录对该 Var 的需求。
 */
Param * replace_outer_var(PlannerInfo *fc_root, Var *fc_var)
{
	Param	   *fc_retval;
	int			fc_i;

	Assert(fc_var->varlevelsup > 0 && fc_var->varlevelsup < fc_root->query_level);

	/* 在适当的 plan_params 中查找 Var，或如果不存在则添加它 */
	fc_i = fc_assign_param_for_var(fc_root, fc_var);

	fc_retval = makeNode(Param);
	fc_retval->paramkind = PARAM_EXEC;
	fc_retval->paramid = fc_i;
	fc_retval->paramtype = fc_var->vartype;
	fc_retval->paramtypmod = fc_var->vartypmod;
	fc_retval->paramcollid = fc_var->varcollid;
	fc_retval->location = fc_var->location;

	return fc_retval;
}

/*
 * 选择一个 PARAM_EXEC 编号来标识给定的 PlaceHolderVar 作为
 * 当前子查询的参数。（它可能已经有一个。）
 * 在适当的上层 root->plan_params 中记录对 PHV 的需求。
 *
 * 这与 assign_param_for_var 类似，除非是 PlaceHolderVars。
 */
static int fc_assign_param_for_placeholdervar(PlannerInfo *fc_root, PlaceHolderVar *fc_phv)
{
	ListCell   *fc_ppl;
	PlannerParamItem *fc_pitem;
	Index		fc_levelsup;

	/* 查找 PHV 所属的查询级别 */
	for (fc_levelsup = fc_phv->phlevelsup; fc_levelsup > 0; fc_levelsup--)
		fc_root = fc_root->parent_root;

	
/*如果那里已经有一个匹配的 PlannerParamItem，就直接使用它*/
	foreach(fc_ppl, fc_root->plan_params)
	{
		fc_pitem = (PlannerParamItem *) lfirst(fc_ppl);
		if (IsA(fc_pitem->item, PlaceHolderVar))
		{
			PlaceHolderVar *fc_pphv = (PlaceHolderVar *) fc_pitem->item;

			/* 我们假设比较 PHID 是足够的 */
			if (fc_pphv->phid == fc_phv->phid)
				return fc_pitem->paramId;
		}
	}

	/* 不，创建一个新的 */
	fc_phv = copyObject(fc_phv);
	IncrementVarSublevelsUp((Node *) fc_phv, -((int) fc_phv->phlevelsup), 0);
	Assert(fc_phv->phlevelsup == 0);

	fc_pitem = makeNode(PlannerParamItem);
	fc_pitem->item = (Node *) fc_phv;
	fc_pitem->paramId = list_length(fc_root->glob->paramExecTypes);
	fc_root->glob->paramExecTypes = lappend_oid(fc_root->glob->paramExecTypes,
											 exprType((Node *) fc_phv->phexpr));

	fc_root->plan_params = lappend(fc_root->plan_params, fc_pitem);

	return fc_pitem->paramId;
}

/*
 * 生成一个 Param 节点以替换给定的 PlaceHolderVar，
 * 预计其 phlevelsup > 0（即，它不是本地的）。
 * 在适当的上层 root->plan_params 中记录对 PHV 的需求。
 *
 * 这与 replace_outer_var 类似，但适用于 PlaceHolderVars。
 */
Param * replace_outer_placeholdervar(PlannerInfo *fc_root, PlaceHolderVar *fc_phv)
{
	Param	   *fc_retval;
	int			fc_i;

	Assert(fc_phv->phlevelsup > 0 && fc_phv->phlevelsup < fc_root->query_level);

	/* 在适当的 plan_params 中查找 PHV，或者如果不存在则添加它 */
	fc_i = fc_assign_param_for_placeholdervar(fc_root, fc_phv);

	fc_retval = makeNode(Param);
	fc_retval->paramkind = PARAM_EXEC;
	fc_retval->paramid = fc_i;
	fc_retval->paramtype = exprType((Node *) fc_phv->phexpr);
	fc_retval->paramtypmod = exprTypmod((Node *) fc_phv->phexpr);
	fc_retval->paramcollid = exprCollation((Node *) fc_phv->phexpr);
	fc_retval->location = -1;

	return fc_retval;
}

/*
 * 生成一个 Param 节点以替换给定的 Aggref
 * 预计其 agglevelsup > 0（即，它不是本地的）。
 * 在适当的上层 root->plan_params 中记录对 Aggref 的需求。
 */
Param * replace_outer_agg(PlannerInfo *fc_root, Aggref *fc_agg)
{
	Param	   *fc_retval;
	PlannerParamItem *fc_pitem;
	Index		fc_levelsup;

	Assert(fc_agg->agglevelsup > 0 && fc_agg->agglevelsup < fc_root->query_level);

	/* 查找 Aggref 所属的查询级别 */
	for (fc_levelsup = fc_agg->agglevelsup; fc_levelsup > 0; fc_levelsup--)
		fc_root = fc_root->parent_root;

	/*
	 * 似乎没有必要尝试去重复对外部聚合的引用。
	 * 每次都只需创建一个新的插槽。
	 */
	fc_agg = copyObject(fc_agg);
	IncrementVarSublevelsUp((Node *) fc_agg, -((int) fc_agg->agglevelsup), 0);
	Assert(fc_agg->agglevelsup == 0);

	fc_pitem = makeNode(PlannerParamItem);
	fc_pitem->item = (Node *) fc_agg;
	fc_pitem->paramId = list_length(fc_root->glob->paramExecTypes);
	fc_root->glob->paramExecTypes = lappend_oid(fc_root->glob->paramExecTypes,
											 fc_agg->aggtype);

	fc_root->plan_params = lappend(fc_root->plan_params, fc_pitem);

	fc_retval = makeNode(Param);
	fc_retval->paramkind = PARAM_EXEC;
	fc_retval->paramid = fc_pitem->paramId;
	fc_retval->paramtype = fc_agg->aggtype;
	fc_retval->paramtypmod = -1;
	fc_retval->paramcollid = fc_agg->aggcollid;
	fc_retval->location = fc_agg->location;

	return fc_retval;
}

/*
 * 生成一个 Param 节点以替换给定的 GroupingFunc 表达式，
 * 预计其 agglevelsup > 0（即，它不是本地的）。
 * 在适当的上层 root->plan_params 中记录对 GroupingFunc 的需求。
 */
Param * replace_outer_grouping(PlannerInfo *fc_root, GroupingFunc *fc_grp)
{
	Param	   *fc_retval;
	PlannerParamItem *fc_pitem;
	Index		fc_levelsup;
	Oid			fc_ptype = exprType((Node *) fc_grp);

	Assert(fc_grp->agglevelsup > 0 && fc_grp->agglevelsup < fc_root->query_level);

	/* 查找 GroupingFunc 所属的查询级别 */
	for (fc_levelsup = fc_grp->agglevelsup; fc_levelsup > 0; fc_levelsup--)
		fc_root = fc_root->parent_root;

	/*
	 * 似乎没有必要尝试去重复对外部聚合的引用。
	 * 每次都只需创建一个新的插槽。
	 */
	fc_grp = copyObject(fc_grp);
	IncrementVarSublevelsUp((Node *) fc_grp, -((int) fc_grp->agglevelsup), 0);
	Assert(fc_grp->agglevelsup == 0);

	fc_pitem = makeNode(PlannerParamItem);
	fc_pitem->item = (Node *) fc_grp;
	fc_pitem->paramId = list_length(fc_root->glob->paramExecTypes);
	fc_root->glob->paramExecTypes = lappend_oid(fc_root->glob->paramExecTypes,
											 fc_ptype);

	fc_root->plan_params = lappend(fc_root->plan_params, fc_pitem);

	fc_retval = makeNode(Param);
	fc_retval->paramkind = PARAM_EXEC;
	fc_retval->paramid = fc_pitem->paramId;
	fc_retval->paramtype = fc_ptype;
	fc_retval->paramtypmod = -1;
	fc_retval->paramcollid = InvalidOid;
	fc_retval->location = fc_grp->location;

	return fc_retval;
}

/*
 * 生成一个 Param 节点以替换给定的 Var，
 * 预计其来自某个上层的 NestLoop 计划节点。
 * 在 root->curOuterParams 中记录对 Var 的需求。
 */
Param * replace_nestloop_param_var(PlannerInfo *fc_root, Var *fc_var)
{
	Param	   *fc_param;
	NestLoopParam *fc_nlp;
	ListCell   *fc_lc;

	/* 该 Var 是否已列在 root->curOuterParams 中？ */
	foreach(fc_lc, fc_root->curOuterParams)
	{
		fc_nlp = (NestLoopParam *) lfirst(fc_lc);
		if (equal(fc_var, fc_nlp->paramval))
		{
			/* 是，因此只需创建一个引用该 NLP 插槽的 Param */
			fc_param = makeNode(Param);
			fc_param->paramkind = PARAM_EXEC;
			fc_param->paramid = fc_nlp->paramno;
			fc_param->paramtype = fc_var->vartype;
			fc_param->paramtypmod = fc_var->vartypmod;
			fc_param->paramcollid = fc_var->varcollid;
			fc_param->location = fc_var->location;
			return fc_param;
		}
	}

	/* 否，因此为一个新的 NLP 分配一个 PARAM_EXEC 插槽 */
	fc_param = generate_new_exec_param(fc_root,
									fc_var->vartype,
									fc_var->vartypmod,
									fc_var->varcollid);
	fc_param->location = fc_var->location;

	/* 将其添加到所需 NLP 的列表中 */
	fc_nlp = makeNode(NestLoopParam);
	fc_nlp->paramno = fc_param->paramid;
	fc_nlp->paramval = copyObject(fc_var);
	fc_root->curOuterParams = lappend(fc_root->curOuterParams, fc_nlp);

	/* 并返回替换的 Param */
	return fc_param;
}

/*
 * 生成一个 Param 节点以替换给定的 PlaceHolderVar，
 * 预计其来自某个上层的 NestLoop 计划节点。
 * 在 root->curOuterParams 中记录对 PHV 的需求。
 *
 * 这与 replace_nestloop_param_var 类似，但适用于 PlaceHolderVars。
 */
Param * replace_nestloop_param_placeholdervar(PlannerInfo *fc_root, PlaceHolderVar *fc_phv)
{
	Param	   *fc_param;
	NestLoopParam *fc_nlp;
	ListCell   *fc_lc;

	/* 该 PHV 是否已列在 root->curOuterParams 中？ */
	foreach(fc_lc, fc_root->curOuterParams)
	{
		fc_nlp = (NestLoopParam *) lfirst(fc_lc);
		if (equal(fc_phv, fc_nlp->paramval))
		{
			/* 是，因此只需创建一个引用该 NLP 插槽的 Param */
			fc_param = makeNode(Param);
			fc_param->paramkind = PARAM_EXEC;
			fc_param->paramid = fc_nlp->paramno;
			fc_param->paramtype = exprType((Node *) fc_phv->phexpr);
			fc_param->paramtypmod = exprTypmod((Node *) fc_phv->phexpr);
			fc_param->paramcollid = exprCollation((Node *) fc_phv->phexpr);
			fc_param->location = -1;
			return fc_param;
		}
	}

	/* 否，因此为一个新的 NLP 分配一个 PARAM_EXEC 插槽 */
	fc_param = generate_new_exec_param(fc_root,
									exprType((Node *) fc_phv->phexpr),
									exprTypmod((Node *) fc_phv->phexpr),
									exprCollation((Node *) fc_phv->phexpr));

	/* 将其添加到所需 NLP 的列表中 */
	fc_nlp = makeNode(NestLoopParam);
	fc_nlp->paramno = fc_param->paramid;
	fc_nlp->paramval = (Var *) copyObject(fc_phv);
	fc_root->curOuterParams = lappend(fc_root->curOuterParams, fc_nlp);

	/* 并返回替换的 Param */
	return fc_param;
}

/*
 * process_subquery_nestloop_params
 *	  处理参数化子查询的参数，这些参数需要从外部 nestloop 中提供。
 *
 * 目前，这些是从当前查询级别要求的子查询在 FROM 中的所有参数，
 * 因为它们必须是 LATERAL 引用。
 *
 * subplan_params 是我们打算传递给 FROM 中的子查询的 PlannerParamItems 列表。
 * （这是在规划子查询时在 root->plan_params 中构造的，
 * 但在调用此时已不再存在。）
 *
 * 子计划对外部变量的引用已经表示为 PARAM_EXEC Params，
 * 因为该转换是通过上面的例程在规划子查询时完成的。
 * 所以我们无需在此修改子计划或 PlannerParamItems。
 * 我们需要做的是向 root->curOuterParams 添加条目，
 * 以指示父 nestloop 计划节点必须提供这些值。
 * 这与 replace_nestloop_param_var 的不同之处在于
 * 使用的 PARAM_EXEC 插槽已经确定。
 *
 * 请注意，我们还使用 root->curOuterRels 作为隐式参数进行合理性检查。
 */
void process_subquery_nestloop_params(PlannerInfo *fc_root, List *fc_subplan_params)
{
	ListCell   *fc_lc;

	foreach(fc_lc, fc_subplan_params)
	{
		PlannerParamItem *fc_pitem = lfirst_node(PlannerParamItem, fc_lc);

		if (IsA(fc_pitem->item, Var))
		{
			Var		   *fc_var = (Var *) fc_pitem->item;
			NestLoopParam *fc_nlp;
			ListCell   *fc_lc;

			/* 如果不是来自 nestloop 外部关系，则抱怨 */
			if (!bms_is_member(fc_var->varno, fc_root->curOuterRels))
				elog(ERROR, "non-LATERAL parameter required by subquery");

			/* 该参数是否已列在 root->curOuterParams 中？ */
			foreach(fc_lc, fc_root->curOuterParams)
			{
				fc_nlp = (NestLoopParam *) lfirst(fc_lc);
				if (fc_nlp->paramno == fc_pitem->paramId)
				{
					Assert(equal(fc_var, fc_nlp->paramval));
					/* 存在，因此无需操作 */
					break;
				}
			}
			if (fc_lc == NULL)
			{
				/* 否，因此添加它 */
				fc_nlp = makeNode(NestLoopParam);
				fc_nlp->paramno = fc_pitem->paramId;
				fc_nlp->paramval = copyObject(fc_var);
				fc_root->curOuterParams = lappend(fc_root->curOuterParams, fc_nlp);
			}
		}
		else if (IsA(fc_pitem->item, PlaceHolderVar))
		{
			PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_pitem->item;
			NestLoopParam *fc_nlp;
			ListCell   *fc_lc;

			/* 如果不是来自 nestloop 外部关系，则抱怨 */
			if (!bms_is_subset(find_placeholder_info(fc_root, fc_phv, false)->ph_eval_at,
							   fc_root->curOuterRels))
				elog(ERROR, "non-LATERAL parameter required by subquery");

			/* 该参数是否已列在 root->curOuterParams 中？ */
			foreach(fc_lc, fc_root->curOuterParams)
			{
				fc_nlp = (NestLoopParam *) lfirst(fc_lc);
				if (fc_nlp->paramno == fc_pitem->paramId)
				{
					Assert(equal(fc_phv, fc_nlp->paramval));
					/* 存在，因此无需操作 */
					break;
				}
			}
			if (fc_lc == NULL)
			{
				/* 否，因此添加它 */
				fc_nlp = makeNode(NestLoopParam);
				fc_nlp->paramno = fc_pitem->paramId;
				fc_nlp->paramval = (Var *) copyObject(fc_phv);
				fc_root->curOuterParams = lappend(fc_root->curOuterParams, fc_nlp);
			}
		}
		else
			elog(ERROR, "unexpected type of subquery parameter");
	}
}

/*
 * 确定任何应由指定左侧关系的 NestLoop 计划节点提供的 NestLoopParams。
 * 从活动的 root->curOuterParams 列表中将它们移除并作为结果列表返回。
 */
List * identify_current_nestloop_params(PlannerInfo *fc_root, Relids fc_leftrelids)
{
	List	   *fc_result;
	ListCell   *fc_cell;

	fc_result = NIL;
	foreach(fc_cell, fc_root->curOuterParams)
	{
		NestLoopParam *fc_nlp = (NestLoopParam *) lfirst(fc_cell);

		/*
		 * 我们正在寻找可以由左侧关系提供的 Vars 和 PHVs。
		 * "bms_overlap" 测试只是一个优化，
		 * 允许在 PHV 无法匹配时跳过 find_placeholder_info()。
		 */
		if (IsA(fc_nlp->paramval, Var) &&
			bms_is_member(fc_nlp->paramval->varno, fc_leftrelids))
		{
			fc_root->curOuterParams = foreach_delete_current(fc_root->curOuterParams,
														  fc_cell);
			fc_result = lappend(fc_result, fc_nlp);
		}
		else if (IsA(fc_nlp->paramval, PlaceHolderVar) &&
				 bms_overlap(((PlaceHolderVar *) fc_nlp->paramval)->phrels,
							 fc_leftrelids) &&
				 bms_is_subset(find_placeholder_info(fc_root,
													 (PlaceHolderVar *) fc_nlp->paramval,
													 false)->ph_eval_at,
							   fc_leftrelids))
		{
			fc_root->curOuterParams = foreach_delete_current(fc_root->curOuterParams,
														  fc_cell);
			fc_result = lappend(fc_result, fc_nlp);
		}
	}
	return fc_result;
}

/*
 * 生成一个不会与任何其他参数冲突的新 Param 节点。
 *
 * 这用于创建表示子计划输出或 NestLoop 参数的 Params。
 *
 * 我们不需要为这样的 Param 构建一个 PlannerParamItem，
 * 但我们确实需要确保我们在 paramExecTypes 中记录类型
 * （否则将不会为其分配插槽）。
 */
Param * generate_new_exec_param(PlannerInfo *fc_root, Oid fc_paramtype, int32 fc_paramtypmod,
						Oid fc_paramcollation)
{
	Param	   *fc_retval;

	fc_retval = makeNode(Param);
	fc_retval->paramkind = PARAM_EXEC;
	fc_retval->paramid = list_length(fc_root->glob->paramExecTypes);
	fc_root->glob->paramExecTypes = lappend_oid(fc_root->glob->paramExecTypes,
											 fc_paramtype);
	fc_retval->paramtype = fc_paramtype;
	fc_retval->paramtypmod = fc_paramtypmod;
	fc_retval->paramcollid = fc_paramcollation;
	fc_retval->location = -1;

	return fc_retval;
}

/*
 * 为特殊参数（即运行时实际不用于传递值的参数）分配一个（非负）PARAM_EXEC ID。
 * 这种参数用于特殊的运行时信号目的，
 * 例如将递归并集节点连接到其工作表扫描节点或在 EvalPlanQual 机制内强制计划重新评估。
 * 然而，没有实际的 Param 节点具有此 ID。
 */
int assign_special_exec_param(PlannerInfo *fc_root)
{
	int			fc_paramId = list_length(fc_root->glob->paramExecTypes);

	fc_root->glob->paramExecTypes = lappend_oid(fc_root->glob->paramExecTypes,
											 InvalidOid);
	return fc_paramId;
}
